/*****************************************************
** Author:
** Date: 2019-07-25
*
*****************************************************/
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include "ads1256.h"
#include "ads1256_reg.h"


#define log_dbg(_fmt_, ...)   \
            printf("[ADS1256][%s:%d][%s][DBG] "_fmt_"\r\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define log_inf(_fmt_, ...)   \
            printf("[ADS1256][%s:%d][%s][INF] "_fmt_"\r\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define log_wrn(_fmt_, ...)   \
            printf("[ADS1256][%s:%d][%s][WRN] "_fmt_"\r\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define log_err(_fmt_, ...)   \
            printf("[ADS1256][%s:%d][%s][ERR] "_fmt_"\r\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)

#define ADS1256_DRDY_TIMEOUT    500 // ms
#define ADS1256_US_DELAY        8

#define ADS1256_SET_CSN_HIGH()   ads1256_obj.spi_csn_level(ADS1256_GPIO_LEVEL_HIGH)
#define ADS1256_SET_CSN_LOW()    ads1256_obj.spi_csn_level(ADS1256_GPIO_LEVEL_LOW)


static const unsigned char ads1256_drate_table[] =
{
    0xF0,   // 30,000 SPS, default
    0xE0,   // 15,000 SPS
    0xD0,   //  7,500 SPS
    0xC0,   //  3,750 SPS
    0xB0,   //  2,000 SPS
    0xA1,   //  1,000 SPS
    0x92,   //    500 SPS
    0x82,   //    100 SPS
    0x72,   //     60 SPS
    0x63,   //     50 SPS
    0x53,   //     30 SPS
    0x43,   //     25 SPS
    0x33,   //     15 SPS
    0x20,   //     10 SPS
    0x13,   //      5 SPS
    0x03,   //      2.5 SPS
};
static ads1256_obj_t ads1256_obj;


/**
 * @brief
 * @param  None
 * @retval None
 */
void ads1256_if_init(ads1256_obj_t *obj)
{
    memcpy(&ads1256_obj, obj, sizeof(ads1256_obj_t));
}

static unsigned char ads1256_read_register(unsigned char reg)
{
    unsigned char val;

    if (   ads1256_obj.spi_csn_level == NULL
        || ads1256_obj.spi_transfer_byte == NULL
        || ads1256_obj.delay_us == NULL)
        return 0x00;

    ADS1256_SET_CSN_LOW();

    ads1256_obj.spi_transfer_byte(ADS1256_CMD_RREG | reg);
    ads1256_obj.spi_transfer_byte(0x00);

    ads1256_obj.delay_us(ADS1256_US_DELAY);
    val = ads1256_obj.spi_transfer_byte(0xff);

    ADS1256_SET_CSN_HIGH();
    return val;
}

static void ads1256_write_register(unsigned char reg, unsigned char *val, int size)
{
    int i;

    if (   ads1256_obj.spi_csn_level == NULL
        || ads1256_obj.spi_transfer_byte == NULL
        || ads1256_obj.delay_us == NULL)
        return ;

    ADS1256_SET_CSN_LOW();

    ads1256_obj.spi_transfer_byte(ADS1256_CMD_WREG | reg);
    ads1256_obj.spi_transfer_byte(size - 1);

    for (i = 0; i < size; i++)
    {
        ads1256_obj.delay_us(ADS1256_US_DELAY);
        ads1256_obj.spi_transfer_byte(val[i]);
    }

    ADS1256_SET_CSN_HIGH();
    return ;
}

static void ads1256_write_cmd(unsigned char cmd)
{
    if (   ads1256_obj.spi_csn_level == NULL
        || ads1256_obj.spi_transfer_byte == NULL)
        return ;

    ADS1256_SET_CSN_LOW();

    ads1256_obj.spi_transfer_byte(cmd);

    ADS1256_SET_CSN_HIGH();
    return ;
}

static int ads1256_wait_drdy(int timeout)
{
    int i = timeout;

    if (ads1256_obj.drdy_level == NULL)
        return -1;

    while (i--)
    {
        if (ads1256_obj.drdy_level() == ADS1256_GPIO_LEVEL_LOW)
            break;

        if (ads1256_obj.delay_ms != NULL)
            ads1256_obj.delay_ms(1);
    }

    if (i == 0)
        return -2;

    // log_dbg("waiting %dms", timeout - i);
    return 0;
}

int ads1256_read_data(void)
{
    unsigned char val[3];
    unsigned int data, i;

    if (   ads1256_obj.spi_csn_level == NULL
        || ads1256_obj.spi_transfer_byte == NULL
        || ads1256_obj.delay_us == NULL)
        return 0x00;

    if (ads1256_wait_drdy(ADS1256_DRDY_TIMEOUT) < 0)
    {
        log_wrn("wait drdy %dms timeout", ADS1256_DRDY_TIMEOUT);
        return 0x00;
    }

    ADS1256_SET_CSN_LOW();

    ads1256_obj.spi_transfer_byte(ADS1256_CMD_RDATA);
    ads1256_obj.delay_us(ADS1256_US_DELAY);

    val[0] = ads1256_obj.spi_transfer_byte(0xff);
    val[1] = ads1256_obj.spi_transfer_byte(0xff);
    val[2] = ads1256_obj.spi_transfer_byte(0xff);

    ADS1256_SET_CSN_HIGH();

    for (i = 0, data = 0; i < 3; i++)
    {
        data <<= 8;
        data |= val[i];
        // log_dbg("data 0x%x val[%d] 0x%x", data, i, val[i]);
    }

    // extend as a signed data
    if (data & 0x00800000)
        data |= 0xff000000;

    return data;
}

unsigned char ads1256_read_status(void)
{
    if (ads1256_wait_drdy(ADS1256_DRDY_TIMEOUT) < 0)
    {
        log_wrn("wait drdy %dms timeout", ADS1256_DRDY_TIMEOUT);
        return 0x00;
    }

    return ads1256_read_register(ADS1256_REG_STATUS);
}

unsigned char ads1256_chipid(void)
{
    return (ads1256_read_status() >> 4);
}

static void ads1256_set_single_ended_channel(int chan)
{
    unsigned char val;

    if (chan > ADS1256_AINCOM)
    {
        log_wrn("chan %d invalid", chan);
        return ;
    }

    val = (chan << 4) | ADS1256_AINCOM;
    // log_dbg("val 0x%x", val);
    ads1256_write_register(ADS1256_REG_MUX, &val, 1);
}

static void ads1256_set_diff_channel(int chan_p, int chan_n)
{
    unsigned char val;

    if (chan_p > ADS1256_AINCOM || chan_n > ADS1256_AINCOM)
    {
        log_wrn("chan_p %d chan_n %d invalid", chan_p, chan_n);
        return ;
    }

    val = (chan_p << 4) | chan_n;
    // log_dbg("val 0x%x", val);
    ads1256_write_register(ADS1256_REG_MUX, &val, 1);
}

void ads1256_cfg(int acal, int bufen, int gain, int drate)
{
    unsigned char val[4];

    if (gain > ADS1256_GAIN_MAX)
    {
        log_wrn("gain %d invalid", gain);
        return ;
    }

    if (drate > ADS1256_DRATE_MAX)
    {
        log_wrn("drate %d invalid", drate);
        return ;
    }

    if (ads1256_wait_drdy(ADS1256_DRDY_TIMEOUT) < 0)
    {
        log_wrn("wait drdy %dms timeout", ADS1256_DRDY_TIMEOUT);
        return ;
    }

    val[0] = (acal << 2) | (bufen << 1);
    val[1] = 0x08;
    val[2] = gain;
    val[3] = ads1256_drate_table[drate];

    ads1256_write_register(ADS1256_REG_STATUS, val, sizeof(val));
}

int ads1256_read_single_ended_channel(int chan)
{
    if (ads1256_obj.delay_us == NULL)
        return -1;

    ads1256_set_single_ended_channel(chan);
    ads1256_obj.delay_us(ADS1256_US_DELAY);
    ads1256_write_cmd(ADS1256_CMD_SYNC);
    ads1256_obj.delay_us(ADS1256_US_DELAY);
    ads1256_write_cmd(ADS1256_CMD_WAKEUP);
    ads1256_obj.delay_us(ADS1256_US_DELAY);
    return ads1256_read_data();
}

int ads1256_read_diff_channel(int chan_p, int chan_n)
{
    if (ads1256_obj.delay_us == NULL)
        return -1;

    ads1256_set_diff_channel(chan_p, chan_n);
    ads1256_obj.delay_us(ADS1256_US_DELAY);
    ads1256_write_cmd(ADS1256_CMD_SYNC);
    ads1256_obj.delay_us(ADS1256_US_DELAY);
    ads1256_write_cmd(ADS1256_CMD_WAKEUP);
    ads1256_obj.delay_us(ADS1256_US_DELAY);
    return ads1256_read_data();
}

int ads1256_scan_single_ended_channels(int channels[], int chan_num, int data[])
{
    int i;

    for (i = 0; i < chan_num; ++i)
    {
        if (ads1256_wait_drdy(ADS1256_DRDY_TIMEOUT) < 0)
        {
            data[i] = 0x00;
            log_wrn("wait drdy %dms timeout", ADS1256_DRDY_TIMEOUT);
            continue ;
        }
        data[i] = ads1256_read_single_ended_channel(channels[i]);
    }
    return i;
}

int ads1256_scan_diff_channels(int chan_p[], int chan_n[], int chan_num, int data[])
{
    int i;

    for (i = 0; i < chan_num; ++i)
    {
        if (ads1256_wait_drdy(ADS1256_DRDY_TIMEOUT) < 0)
        {
            log_wrn("wait drdy %dms timeout", ADS1256_DRDY_TIMEOUT);
            continue ;
        }
        data[i] = ads1256_read_diff_channel(chan_p[i], chan_n[i]);
    }
    return i;
}

void ads1256_read_single_ended_channel_continuous(int chan, int spl_num, int data[], int us_time[])
{
    struct timeval tv;
    unsigned long init_time;
    unsigned char val[3];
    int rd_data, i, j;

    if (ads1256_obj.delay_us == NULL)
        return ;

    if (ads1256_wait_drdy(ADS1256_DRDY_TIMEOUT) < 0)
    {
        log_wrn("wait drdy %dms timeout", ADS1256_DRDY_TIMEOUT);
        return ;
    }

    // Set single-ended analog input channel.
    ads1256_set_single_ended_channel(chan);
    ads1256_obj.delay_us(ADS1256_US_DELAY);
    ads1256_obj.delay_us(ADS1256_US_DELAY);
    ads1256_write_cmd(ADS1256_CMD_SYNC);
    ads1256_obj.delay_us(ADS1256_US_DELAY);
    ads1256_write_cmd(ADS1256_CMD_WAKEUP);
    ads1256_obj.delay_us(ADS1256_US_DELAY);

    // Set continuous mode.
    ADS1256_SET_CSN_LOW();

    if (ads1256_wait_drdy(ADS1256_DRDY_TIMEOUT) < 0)
    {
        log_wrn("wait drdy %dms timeout", ADS1256_DRDY_TIMEOUT);
        return ;
    }

    ads1256_write_cmd(ADS1256_CMD_RDATAC);
    ads1256_obj.delay_us(ADS1256_US_DELAY); // min delay: t6 = 50 * 1/7.68 MHz = 6.5 microseconds

    // Start reading data initialize time
    gettimeofday(&tv, NULL);
    init_time = tv.tv_sec * 1000000 + tv.tv_usec;
    log_dbg("init time %lu", init_time);

    for (i = 0; i < spl_num; ++i)
    {
        if (ads1256_wait_drdy(ADS1256_DRDY_TIMEOUT) < 0)
        {
            log_wrn("wait drdy %dms timeout", ADS1256_DRDY_TIMEOUT);
            continue ;
        }

        val[0] = ads1256_obj.spi_transfer_byte(0xff);
        val[1] = ads1256_obj.spi_transfer_byte(0xff);
        val[2] = ads1256_obj.spi_transfer_byte(0xff);

        for (j = 0, rd_data = 0; j < 3; j++)
        {
            rd_data <<= 8;
            rd_data |= val[j];
            log_dbg("rd_data 0x%x val[%d] 0x%x", rd_data, j, val[j]);
        }

        // extend as a signed rd_data
        if (rd_data & 0x00800000)
            rd_data |= 0xff000000;

        data[i] = rd_data;

        gettimeofday(&tv, NULL);
        us_time[i] = (tv.tv_sec * 1000000 + tv.tv_usec) - init_time;

        ads1256_obj.delay_us(ADS1256_US_DELAY);
    }

    // Stop continuous mode.
    if (ads1256_wait_drdy(ADS1256_DRDY_TIMEOUT) < 0)
    {
        log_wrn("wait drdy %dms timeout", ADS1256_DRDY_TIMEOUT);
        return ;
    }

    ads1256_write_cmd(ADS1256_CMD_SDATAC); // Stop read data continuous.

    ADS1256_SET_CSN_HIGH();
}

void ads1256_read_diff_channel_continuous(int chan_p, int chan_n, int spl_num, int data[], int us_time[])
{
    struct timeval tv;
    unsigned long init_time;
    unsigned char val[3];
    int rd_data, i, j;

    if (ads1256_obj.delay_us == NULL)
        return ;

    // Set differential analog input channel.
    ads1256_set_diff_channel(chan_p, chan_n);
    ads1256_obj.delay_us(ADS1256_US_DELAY);

    // Set continuous mode.
    ADS1256_SET_CSN_LOW();

    if (ads1256_wait_drdy(ADS1256_DRDY_TIMEOUT) < 0)
    {
        log_wrn("wait drdy %dms timeout", ADS1256_DRDY_TIMEOUT);
        return ;
    }

    ads1256_write_cmd(ADS1256_CMD_RDATAC);
    ads1256_obj.delay_us(ADS1256_US_DELAY); // min delay: t6 = 50 * 1/7.68 MHz = 6.5 microseconds

    // Start reading data initialize time
    gettimeofday(&tv, NULL);
    init_time = tv.tv_sec * 1000000 + tv.tv_usec;

    for (i = 0; i < spl_num; ++i)
    {
        if (ads1256_wait_drdy(ADS1256_DRDY_TIMEOUT) < 0)
        {
            log_wrn("wait drdy %dms timeout", ADS1256_DRDY_TIMEOUT);
            continue ;
        }

        val[0] = ads1256_obj.spi_transfer_byte(0xff);
        val[1] = ads1256_obj.spi_transfer_byte(0xff);
        val[2] = ads1256_obj.spi_transfer_byte(0xff);

        for (j = 0, rd_data = 0; j < 3; j++)
        {
            rd_data <<= 8;
            rd_data |= val[j];
            // log_dbg("rd_data 0x%x val[%d] 0x%x", rd_data, j, val[j]);
        }

        // extend as a signed rd_data
        if (rd_data & 0x00800000)
            rd_data |= 0xff000000;

        data[i] = rd_data;

        gettimeofday(&tv, NULL);
        us_time[i] = (tv.tv_sec * 1000000 + tv.tv_usec) - init_time;

        ads1256_obj.delay_us(ADS1256_US_DELAY);
    }

    // Stop continuous mode.
    if (ads1256_wait_drdy(ADS1256_DRDY_TIMEOUT) < 0)
    {
        log_wrn("wait drdy %dms timeout", ADS1256_DRDY_TIMEOUT);
        return ;
    }

    ads1256_write_cmd(ADS1256_CMD_SDATAC); // Stop read data continuous.

    ADS1256_SET_CSN_HIGH();
}

/* End of file */



